package ch.idsia.utils.wox.serial;

import org.jdom.Comment;
import org.jdom.Element;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Vector;


// import crjaim.EncodeBase64;

/**
 * A simple but useful Object to XML serialiser.
 * By Simon M. Lucas, August 2004
 * Base 64 modifications by Carlos R. Jaimez Gonzalez
 */

public class SimpleWriter implements ObjectWriter
{

    HashMap map;

    int count;
    boolean writePrimitiveTypes = true;
    boolean doStatic = true;

    // not much point writing out final values - at least yet -
    // the reader is not able to set them (though there's probably
    // a hidden way of doing this
    boolean doFinal = false;


    public SimpleWriter()
    {
        //System.out.println("inside SimpleWriter Constructor...");
        map = new HashMap();
        count = 0;

    }

    public Element write(Object ob)
    {
        Element el;
        if (ob == null)
        {
            // a null object is represented by an empty Object tag with no attributes
            return new Element(OBJECT);
        }
        if (map.get(ob) != null)
        {
            el = new Element(OBJECT);
            el.setAttribute(IDREF, map.get(ob).toString());
            return el;
        }
        // a previously unseen object...
        map.put(ob, new Integer(count++));
        if (Util.stringable(ob))
        {
            el = new Element(OBJECT);
            el.setAttribute(TYPE, ob.getClass().getName());
            el.setText(stringify(ob));
        } else if (ob.getClass().isArray())
        {
            el = writeArray(ob);
        } else
        {
            el = new Element(OBJECT);
            el.setAttribute(TYPE, ob.getClass().getName());
            writeFields(ob, el);
        }
        el.setAttribute(ID, map.get(ob).toString());
        return el;
    }

    public Element writeArray(Object ob)
    {
        if (isPrimitiveArray(ob.getClass()))
        {
            return writePrimitiveArray(ob);
        } else
        {
            return writeObjectArray(ob);
        }
    }

    public Element writeObjectArray(Object ob)
    {
        Element el = new Element(ARRAY);
        // el.setAttribute
        // int[].class.
        // Array.
        el.setAttribute(TYPE, ob.getClass().getComponentType().getName());
        int len = Array.getLength(ob);
        el.setAttribute(LENGTH, "" + len);
        for (int i = 0; i < len; i++)
        {
            el.addContent(write(Array.get(ob, i)));
        }
        return el;
    }

    public Element writePrimitiveArray(Object ob)
    {
        Element el = new Element(ARRAY);
        el.setAttribute(TYPE, ob.getClass().getComponentType().getName());
        int len = Array.getLength(ob);
        //CJ this should not be here beacsue the lenght for the byte[] can be different
        //el.setAttribute(LENGTH, "" + len);
        if (ob instanceof byte[])
        {
            el.setText(byteArrayString((byte[]) ob, el));
        } else
        {
            el.setAttribute(LENGTH, "" + len);
            el.setText(arrayString(ob, len));
        }
        return el;
    }

    //method modified to include base64 encoding

    public String byteArrayString(byte[] a, Element e)
    {
        byte[] target = EncodeBase64.encode(a);
        //set the lenght fro the new encoded array
        e.setAttribute(LENGTH, "" + target.length);
        String strTarget = new String(target);
        return strTarget;
    }

    public String arrayString(Object ob, int len)
    {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < len; i++)
        {
            if (i > 0)
            {
                sb.append(" ");
            }
            sb.append(Array.get(ob, i).toString());
        }
        return sb.toString();
    }


    public void writeFields(Object o, Element parent)
    {
        // get the class of the object
        // get its fields
        // then get the value of each one
        // and call write to put the value in the Element

        Class cl = o.getClass();
        Field[] fields = getFields(cl);
        String name = null;
        for (int i = 0; i < fields.length; i++)
        {
            if ((doStatic || !Modifier.isStatic(fields[i].getModifiers())) &&
                    (doFinal || !Modifier.isFinal(fields[i].getModifiers())))
                try
                {
                    fields[i].setAccessible(true);
                    name = fields[i].getName();
                    // need to handle shadowed fields in some way...
                    // one way is to add info about the declaring class
                    // but this will bloat the XML file if we di it for
                    // every field - might be better to just do it for
                    // the shadowed fields
                    // name += "." + fields[i].getDeclaringClass().getName();
                    // fields[i].
                    Object value = fields[i].get(o);
                    Element field = new Element(FIELD);
                    field.setAttribute(NAME, name);
                    if (shadowed(fields, name))
                    {
                        field.setAttribute(DECLARED, fields[i].getDeclaringClass().getName());
                    }
                    if (fields[i].getType().isPrimitive())
                    {
                        // this is not always necessary - so it's optional
                        if (writePrimitiveTypes)
                        {
                            field.setAttribute(TYPE, fields[i].getType().getName());
                        }
                        field.setAttribute(VALUE, value.toString());

                    } else
                    {
                        field.addContent(write(value));
                    }
                    parent.addContent(field);
                } catch (Exception e)
                {
                    e.printStackTrace();
                    System.out.println(e);
                    // at least comment on what went wrong
                    parent.addContent(new Comment(e.toString()));
                }
        }
    }

    private boolean shadowed(Field[] fields, String fieldName)
    {
        // count the number of fields with the name fieldName
        // return true if greater than 1
        int count = 0;
        for (int i = 0; i < fields.length; i++)
        {
            if (fieldName.equals(fields[i].getName()))
            {
                count++;
            }
        }
        return count > 1;
    }

    public static String stringify(Object ob)
    {
        if (ob instanceof Class)
        {
            return ((Class) ob).getName();
        } else
        {
            return ob.toString();
        }
    }

    public static Field[] getFields(Class c)
    {
        Vector v = new Vector();
        while (!(c == null))
        { // c.equals( Object.class ) ) {
            Field[] fields = c.getDeclaredFields();
            for (int i = 0; i < fields.length; i++)
            {
                // System.out.println(fields[i]);
                v.addElement(fields[i]);
            }
            c = c.getSuperclass();
        }
        Field[] f = new Field[v.size()];
        for (int i = 0; i < f.length; i++)
        {
            f[i] = (Field) v.get(i);
        }
        return f;
    }

    public static Object[] getValues(Object o, Field[] fields)
    {
        Object[] values = new Object[fields.length];
        for (int i = 0; i < fields.length; i++)
        {
            try
            {
                fields[i].setAccessible(true);
                values[i] = fields[i].get(o);
                System.out.println(fields[i].getName() + "\t " + values[i]);
            } catch (Exception e)
            {
                System.out.println(e);
            }
        }
        return values;
    }

    public boolean isPrimitiveArray(Class c)
    {
        for (int i = 0; i < primitiveArrays.length; i++)
        {
            if (c.equals(primitiveArrays[i]))
            {
                return true;
            }
        }
        return false;
    }


}
